package com.gosling.cloudspace.tools;

import com.google.common.base.Preconditions;
import org.apache.commons.lang3.StringUtils;
import org.bouncycastle.crypto.CipherParameters;
import org.bouncycastle.crypto.Digest;
import org.bouncycastle.crypto.digests.SM3Digest;
import org.bouncycastle.crypto.engines.SM2Engine;
import org.bouncycastle.crypto.params.ECPrivateKeyParameters;
import org.bouncycastle.crypto.params.ECPublicKeyParameters;
import org.bouncycastle.crypto.params.ParametersWithRandom;
import org.bouncycastle.crypto.signers.SM2Signer;
import org.bouncycastle.jcajce.provider.asymmetric.util.ECUtil;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.bouncycastle.util.encoders.Base64;
import org.bouncycastle.util.encoders.Hex;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.util.Base64Utils;

import java.nio.charset.StandardCharsets;
import java.security.*;
import java.security.spec.*;
import java.util.HashMap;
import java.util.Map;
import java.util.regex.Pattern;

/**
 * 生成签名,签名认证,校验
 * SM3withSm2
 *
 * @email 1793327033@qq.com
 * @classname Sm2Tool
 * @author: osc_lihonglin
 */
public class Sm2Tool {
    private final static Digest DIGEST = new SM3Digest();
    private final static Pattern COMPILE = Pattern.compile("\\s*|\t|\r|\n|\r\n");
    protected static Logger logger = LoggerFactory.getLogger(Sm2Tool.class);

    /***
     * zh中文转Unicode
     * @param cn
     * @return
     */
    public static String cnToUnicode(String cn) {
        char[] chars = cn.toCharArray();
        StringBuilder returnStr = new StringBuilder(1024);
        for (char aChar : chars) {
            returnStr.append("\\u").append(Integer.toString(aChar, 16));
        }
        return returnStr.toString();
    }

    /**
     * 解密
     *
     * @param data       数据
     * @param privateKey 私钥
     * @return 解密之后的数据
     */
    public static byte[] decrypt(byte[] data, byte[] privateKey) throws Exception {
        CipherParameters privateKeyParameters = privateKeyToParams("SM2", privateKey);
        SM2Engine engine = new SM2Engine(DIGEST);
        engine.init(false, privateKeyParameters);
        logger.debug("data:" + data);
        logger.debug("data.length" + data.length);
        return engine.processBlock(data, 0, data.length);
    }

    /**
     * 加密
     *
     * @param data      数据
     * @param publicKey 公钥
     * @return 加密之后的数据
     */
    public static byte[] encrypt(byte[] data, byte[] publicKey) throws Exception {
        CipherParameters pubKeyParameters = new ParametersWithRandom(publicKeyToParams("SM2", publicKey));
        SM2Engine engine = new SM2Engine(DIGEST);
        engine.init(true, pubKeyParameters);
        return engine.processBlock(data, 0, data.length);
    }

    /**
     * 签名
     *
     * @param data 数据
     * @return 签名
     */
    public static byte[] sign(byte[] data, byte[] privateKey) throws Exception {
        SM2Signer signer = new SM2Signer();
        CipherParameters param = new ParametersWithRandom(privateKeyToParams("SM2", privateKey));
        signer.init(true, param);
        signer.update(data, 0, data.length);
        return signer.generateSignature();
    }

    /**
     * 用公钥检验数字签名的合法性
     *
     * @param data      数据
     * @param sign      签名
     * @param publicKey 公钥
     * @return 是否验证通过
     */
    public static boolean verify(byte[] data, byte[] sign, byte[] publicKey) throws Exception {
        SM2Signer signer = new SM2Signer();
        CipherParameters param = publicKeyToParams("SM2", publicKey);
        signer.init(false, param);
        signer.update(data, 0, data.length);
        return signer.verifySignature(sign);
    }

    /**
     * 私钥转换为 {@link ECPrivateKeyParameters}
     *
     * @param key key
     * @return
     * @throws InvalidKeyException
     */
    private static ECPrivateKeyParameters privateKeyToParams(String algorithm, byte[] key) throws InvalidKeyException, InvalidKeySpecException, NoSuchAlgorithmException {
        Preconditions.checkNotNull(key, "key must be not null !");
        PrivateKey privateKey = generatePrivateKey(algorithm, key);
        return (ECPrivateKeyParameters) ECUtil.generatePrivateKeyParameter(privateKey);
    }

    /**
     * 生成私钥
     *
     * @param algorithm 算法
     * @param key       key
     * @return
     */
    private static PrivateKey generatePrivateKey(String algorithm, byte[] key) throws NoSuchAlgorithmException, InvalidKeySpecException {
        Preconditions.checkNotNull(algorithm, "algorithm must be not null !");
        Preconditions.checkNotNull(key, "key must be not null !");
        KeySpec keySpec = new PKCS8EncodedKeySpec(key);
        algorithm = getAlgorithmAfterWith(algorithm);
        return getKeyFactory(algorithm).generatePrivate(keySpec);
    }

    /**
     * 公钥转换为 {@link ECPublicKeyParameters}
     *
     * @param key key
     * @return
     * @throws InvalidKeyException
     */
    private static ECPublicKeyParameters publicKeyToParams(String algorithm, byte[] key) throws InvalidKeyException, InvalidKeySpecException, NoSuchAlgorithmException {
        Preconditions.checkNotNull(key, "key must be not null !");
        PublicKey publicKey = generatePublicKey(algorithm, key);
        return (ECPublicKeyParameters) ECUtil.generatePublicKeyParameter(publicKey);
    }

    /**
     * 生成公钥
     *
     * @param algorithm 算法
     * @param key       key
     * @return
     */
    private static PublicKey generatePublicKey(String algorithm, byte[] key) throws NoSuchAlgorithmException, InvalidKeySpecException {
        Preconditions.checkNotNull(algorithm, "algorithm must be not null !");
        Preconditions.checkNotNull(key, "key must be not null !");
        KeySpec keySpec = new X509EncodedKeySpec(key);
        algorithm = getAlgorithmAfterWith(algorithm);
        return getKeyFactory(algorithm).generatePublic(keySpec);
    }

    /**
     * 获取用于密钥生成的算法<br>
     * 获取XXXwithXXX算法的后半部分算法，如果为ECDSA或SM2，返回算法为EC
     *
     * @param algorithm XXXwithXXX算法
     * @return 算法
     */
    private static String getAlgorithmAfterWith(String algorithm) {
        Preconditions.checkNotNull(algorithm, "algorithm must be not null !");
        int indexOfWith = StringUtils.lastIndexOfIgnoreCase(algorithm, "with");
        if (indexOfWith > 0) {
            algorithm = StringUtils.substring(algorithm, indexOfWith + "with".length());
        }
        if ("ECDSA".equalsIgnoreCase(algorithm) || "SM2".equalsIgnoreCase(algorithm)) {
            algorithm = "EC";
        }
        return algorithm;
    }

    /**
     * 获取{@link KeyFactory}
     *
     * @param algorithm 非对称加密算法
     * @return {@link KeyFactory}
     */
    private static KeyFactory getKeyFactory(String algorithm) throws NoSuchAlgorithmException {
        final Provider provider = new BouncyCastleProvider();
        return KeyFactory.getInstance(algorithm, provider);
    }

    /**
     * SM2算法生成密钥对
     *
     * @return 密钥对信息
     */
    public static KeyPair generateSm2KeyPair() {
        try {
            final ECGenParameterSpec sm2Spec = new ECGenParameterSpec("sm2p256v1");
            // 获取一个椭圆曲线类型的密钥对生成器
            final KeyPairGenerator kpg = KeyPairGenerator.getInstance("EC", new BouncyCastleProvider());
            SecureRandom random = new SecureRandom();
            // 使用SM2的算法区域初始化密钥生成器
            kpg.initialize(sm2Spec, random);
            // 获取密钥对
            return kpg.generateKeyPair();
        } catch (Exception e) {
            logger.error("generate sm2 key pair failed:{}" + e.getMessage() + e);
            return null;
        }
    }

    public static void main(String[] args) throws Exception {
        KeyPair keyPair = generateSm2KeyPair();
        String orgText = "test12李宏林";
        System.out.println("加密前的内容: " + orgText);
        //加密
        byte[] ciphertext = Base64Utils.encode(encrypt(orgText.getBytes(StandardCharsets.UTF_8), keyPair.getPublic().getEncoded()));
        String toString = new String(ciphertext);
        System.out.println("加密结果: " + toString);
        //生成签名
        byte[] signatureByte = Base64Utils.encode(sign(orgText.getBytes(StandardCharsets.UTF_8), keyPair.getPrivate().getEncoded()));
        String signature = new String(signatureByte);
        System.out.println("签名结果: " + signature);
        //解密
        byte[] decrypt = decrypt(Base64Utils.decode(ciphertext), keyPair.getPrivate().getEncoded());
        String newText = new String(decrypt, StandardCharsets.UTF_8);
        System.out.println("解密后的内容: " + newText);
        //验签
        boolean result = verify(decrypt, Base64Utils.decode(signatureByte), keyPair.getPublic().getEncoded());
        System.out.println("解密后内容是否一致: " + result);
    }

    /***
     * 验签、解签
     *
     * @param content
     * @param publicKey
     * @param signKey
     * @return boolean
     */
    public static boolean unsignData(String content, String publicKey, String signKey) {
        //转unicode码
        String destUnicode = cnToUnicode(content);
        final BouncyCastleProvider bc = new BouncyCastleProvider();
        try {
            KeyFactory keyFact = KeyFactory.getInstance("EC", bc);
            // 根据采用的编码结构反序列化公私钥
            PublicKey pub = keyFact.generatePublic(new X509EncodedKeySpec(Base64.decode(publicKey)));
            Signature signature = Signature.getInstance("SM3withSm2", bc);
            signature.initVerify(pub);
            signature.update(destUnicode.getBytes());
            // 验证签名值
            //验证签名结果:新旧数据不一致,已经作出修改 || 新旧数据一致,未在原基础上作出修改
            return signature.verify(Hex.decode(signKey));
        } catch (Exception e) {
            return false;
        }
    }

    /**
     * 初次加签
     *
     * @param content 被签名内容
     * @return Map<String, String>
     */
    public static Map<String, String> encryptXml(String content) {
        try {
            logger.debug("签名开始------>>>");
            /** SM2非对称算法密钥对生成 */
            // 获取SM2椭圆曲线的参数
            final ECGenParameterSpec sm2Spec = new ECGenParameterSpec("sm2p256v1");
            // 获取一个椭圆曲线类型的密钥对生成器
            final KeyPairGenerator kpg = KeyPairGenerator.getInstance("EC", new BouncyCastleProvider());
            // 使用SM2参数初始化生成器
            kpg.initialize(sm2Spec);
            // 获取密钥对
            KeyPair keyPair = kpg.generateKeyPair();
            PublicKey publicKey = keyPair.getPublic();
            PrivateKey privateKey = keyPair.getPrivate();
            // 生成SM2sign with sm3 签名验签算法实例
            Signature signature = Signature.getInstance("SM3withSm2", new BouncyCastleProvider());
            // 签名需要使用私钥，使用私钥 初始化签名实例
            signature.initSign(privateKey);
            //加签原内容 字符串去掉tab(0x0009)、回车(0x000D)、换行(0x000A)和空格(0x0020) 转unicode码 原文转二进制
            String destUnicode = cnToUnicode(COMPILE.matcher(content).replaceAll(""));
            // 写入签名原文到算法中
            signature.update(destUnicode.getBytes());
            // 计算签名值
            byte[] signatureValue = signature.sign();
            System.out.printf("signature[%d]: %s\n", signatureValue.length, Hex.toHexString(signatureValue));
            logger.debug("<<<------签名结束");
            return new HashMap<String, String>(8) {{
                //公匙（证书） 证书（M62）应采用Base64进行编码。
                put("publicKey", Base64.toBase64String(publicKey.getEncoded()));
                //签名结果（未进行base64编码的结果）
                put("signatureValue", Hex.toHexString(signatureValue));
                //签名结果（M61）采用Base64进行编码后的签名结果。
                put("siginKey", Base64.toBase64String(signatureValue));
                //算法标识符
                put("algorithm", "sm2");
            }};
        } catch (Exception e) {
            e.printStackTrace();
            return new HashMap<String, String>(8) {{
                put("publicKey", "");
                put("siginKey", "");
                put("signatureValue", "");
                put("algorithm", "sm2");
            }};
        }

    }

}
